using System.Reflection;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using Terraria.ModLoader;
using Terraria.ModLoader.Core;

namespace RSCore;

public class LdstrHook : IDisposable
{
	public static LdstrHook Instance => instance ??= new LdstrHook();

	public void AddTranslation(Dictionary<string, TextPair[]> ldstrText, string modName)
	{
		if (_translations.TryGetValue(modName, out var dict))
		{
			foreach (var pair in ldstrText)
			{
				dict[pair.Key] = pair.Value;
			}
		}
		else
		{
			_translations[modName] = ldstrText;
		}
	}

	public void Dispose()
	{
		_hookManager.Dispose();
		_translations.Clear();
		instance = null;
		GC.SuppressFinalize(this);
	}

	private static LdstrHook? instance;

	private HookManager _hookManager = new HookManager();

	private Dictionary<string, Dictionary<string, TextPair[]>> _translations = new();

	private LdstrHook()
	{
		var method = AssemblyManager.Instantiate;
		_hookManager.AddHook(method.Method, InstantiateHook);
	}

	private void Apply(Assembly assembly, string modName)
	{
		if (!_translations.TryGetValue(modName, out var translations))
		{
			return;
		}

		Dictionary<string, MethodBase> methodTable = new();
		foreach (var type in assembly.DefinedTypes)
		{
			foreach (var method in type.DeclaredMethods)
			{
				methodTable[Wrapper.Create(method)] = method;
			}
			foreach (var method in type.DeclaredConstructors)
			{
				methodTable[Wrapper.Create(method)] = method;
			}
		}

		foreach (var (methodName, text) in translations)
		{
			if (!methodTable.TryGetValue(methodName, out var method))
			{
				ModHelper.Info("MethodNotFound", methodName);
				continue;
			}

			var map = new Dictionary<string, string>();
			foreach (var (origin, translation) in text)
			{
				if (map.TryGetValue(origin, out var exist) && exist != translation)
				{
					ModHelper.Info("TranslationNotSame", origin, exist, translation);
					continue;
				}
				map[origin] = translation;
			}

			if (map.Count == 0)
			{
				continue;
			}

			_hookManager.AddHook(method, (ILContext il) =>
			{
				foreach (var ins in il.Instrs)
				{
					if (ins.OpCode == OpCodes.Ldstr && map.TryGetValue(ins.Operand as string ?? string.Empty, out var current))
					{
						ins.Operand = current;
					}
				}
			});
		}
	}

	private Mod InstantiateHook(Func<AssemblyManager.ModLoadContext, Mod> orig, AssemblyManager.ModLoadContext mod)
	{
		if (mod.Name != null)
		{
			Apply(mod.assembly, mod.Name);
		}
		return orig(mod);
	}
}